package com.tjc.cloud.util.isv

import com.tjc.cloud.util.http.status.HttpException
import com.tjc.cloud.util.json.ext.toJson
import com.tjc.cloud.util.json.ext.toModel
import com.tjc.cloud.util.signature.base64DecodeBytes
import com.tjc.cloud.util.signature.des3
import com.tjc.cloud.util.signature.des3Decode
import com.tjc.cloud.util.signature.md5
import org.springframework.core.io.UrlResource
import org.springframework.http.MediaType
import org.springframework.http.client.MultipartBodyBuilder
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import reactor.core.publisher.toMono
class IsvClient(var isvProperties: IsvProperties) {
    val merchantNo = isvProperties.merchantNo
    val ruleKey = isvProperties.ruleKey
    fun request(interfaceName: String, param: Any): Mono<Map<String, *>?> =
        this.request(interfaceName,param.toJson()!!)
    fun request(interfaceName: String, param: String): Mono<Map<String, *>?> =
            Mono.justOrEmpty(param).flatMap { json ->
                val data = json.des3(isvProperties.encryptKey.toByteArray())
                WebClient.create(isvProperties.serverUrl)
                        .post().contentType(MediaType.APPLICATION_FORM_URLENCODED)
                        .body(BodyInserters.fromFormData("interfaceName", interfaceName)
                                .with("body", data)
                                .with("sign", "$data&${isvProperties.merchantNo}&${isvProperties.signKey}".md5())
                                .with("merchantNo", isvProperties.merchantNo)
                        )
                        .retrieve().bodyToMono(Map::class.java).log()
                        .flatMap { res ->
                            res.toMono().filter {
                                it["success"] == true &&
                                        it["code"] == "0000"
                                        && "${it["data"]}&${isvProperties.signKey}".md5() == it["sign"]
                            }.switchIfEmpty(Mono.error(HttpException.BAD_REQUEST(res["message"] as String?))).map {
                                (it["data"] as String)
                                        .base64DecodeBytes()
                                        .des3Decode(isvProperties.encryptKey
                                                .toByteArray()).toModel<Map<String, *>>()
                            }

                        }
            }

    fun upload(interfaceName: String, param: Any, file: String) =
            Mono.justOrEmpty<String>(param.toJson()).flatMap { json ->
                val data = param.toJson()!!.des3(isvProperties.encryptKey.toByteArray())
                val body = MultipartBodyBuilder()
                body.part("interfaceName", interfaceName)
                body.part("body", data)
                body.part("sign", "$data&$merchantNo&${isvProperties.signKey}".md5())
                body.part("merchantNo", merchantNo)
                body.part("file", UrlResource(file))
                WebClient.create("http://entry.trx.helipay.com/trx/merchantEntry/upload.action").post()
                        .contentType(MediaType.MULTIPART_FORM_DATA)
                        .body(BodyInserters.fromMultipartData(body.build()))
                        .retrieve().bodyToMono(Map::class.java)
                        .flatMap { res ->
                            res.toMono().filter {
                                it["success"] == true &&
                                        it["code"] == "0000"
                                        && "${it["data"]}&${isvProperties.signKey}".md5() == it["sign"]
                            }.switchIfEmpty(Mono.error(HttpException.BAD_REQUEST(res["message"] as String?))).map {
                                (it["data"] as String)
                                        .base64DecodeBytes()
                                        .des3Decode(isvProperties.encryptKey
                                                .toByteArray()).toModel<Map<String, *>>()
                            }

                        }
            }


    fun payment(map: Map<String, *>): Mono<Map<*, *>> {
        fun String.kid() = try {
            this.replace("P", "").split("_")[0].toInt()
        } catch (e: Exception) {
            -1
        }

        fun Map<String, *>.sign() =
                this.toMono().log().flatMapIterable {
                    it.entries
                }.filter { it.key.kid() > 0 }
                        .sort { o1, o2 -> o1.key.kid().compareTo(o2.key.kid()) }
                        .map { it.value }
                        .reduce("") { t, u -> "$t&$u" }.log().map {
                            "$it&${isvProperties.payKey}".md5()
                        }

        return map.sign()
                .flatMap {
                    map.toMono().flatMapIterable { it.entries }.filter { it.value != "" }.reduce(BodyInserters.fromFormData("sign", it)) { t, u ->
                        t.with(u.key, u.value.toString())
                    }
                }
                .flatMap {
                    WebClient.create(isvProperties.paymentUrl).post().contentType(MediaType.APPLICATION_FORM_URLENCODED)
                            .body(it)
                            .retrieve().bodyToMono(String::class.java)
                            .log().map {
                                it.toModel<Map<String, *>>() }
                }

    }
}

